package demoMod.invincibleOne.relics;

import basemod.ReflectionHacks;
import basemod.patches.com.megacrit.cardcrawl.cards.AbstractCard.CardModifierPatches;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.evacipated.cardcrawl.mod.stslib.relics.ClickableRelic;
import com.evacipated.cardcrawl.modthespire.lib.SpireInsertPatch;
import com.evacipated.cardcrawl.modthespire.lib.SpirePatch;
import com.evacipated.cardcrawl.modthespire.lib.SpirePostfixPatch;
import com.evacipated.cardcrawl.modthespire.lib.SpireReturn;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.megacrit.cardcrawl.actions.AbstractGameAction;
import com.megacrit.cardcrawl.actions.animations.TalkAction;
import com.megacrit.cardcrawl.actions.animations.VFXAction;
import com.megacrit.cardcrawl.actions.common.ApplyPowerAction;
import com.megacrit.cardcrawl.actions.utility.UseCardAction;
import com.megacrit.cardcrawl.cards.AbstractCard;
import com.megacrit.cardcrawl.cards.DamageInfo;
import com.megacrit.cardcrawl.characters.AbstractPlayer;
import com.megacrit.cardcrawl.core.CardCrawlGame;
import com.megacrit.cardcrawl.core.Settings;
import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
import com.megacrit.cardcrawl.dungeons.Exordium;
import com.megacrit.cardcrawl.dungeons.TheCity;
import com.megacrit.cardcrawl.map.MapRoomNode;
import com.megacrit.cardcrawl.monsters.AbstractMonster;
import com.megacrit.cardcrawl.monsters.ending.CorruptHeart;
import com.megacrit.cardcrawl.powers.MetallicizePower;
import com.megacrit.cardcrawl.powers.StrengthPower;
import com.megacrit.cardcrawl.rooms.*;
import com.megacrit.cardcrawl.screens.stats.RunData;
import com.megacrit.cardcrawl.ui.buttons.ProceedButton;
import com.megacrit.cardcrawl.vfx.UpgradeShineEffect;
import com.megacrit.cardcrawl.vfx.cardManip.ShowCardAndObtainEffect;
import com.megacrit.cardcrawl.vfx.cardManip.ShowCardBrieflyEffect;
import demoMod.invincibleOne.InvincibleOneMod;
import demoMod.invincibleOne.cards.invincible.NoOneLives;
import demoMod.invincibleOne.characters.InvincibleOne;
import demoMod.invincibleOne.interfaces.EnterRoomHook;
import demoMod.invincibleOne.interfaces.MonsterDeathHook;
import demoMod.invincibleOne.interfaces.OnRestHook;
import demoMod.invincibleOne.interfaces.SpawnMonsterHook;
import demoMod.invincibleOne.skins.Skin;
import demoMod.invincibleOne.ui.BingoSlot;

import java.io.File;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.megacrit.cardcrawl.core.CardCrawlGame.languagePack;

public class TaskGoal extends AbstractInvincibleRelic implements ClickableRelic {
    public static final String ID = InvincibleOneMod.makeID("TaskGoal");
    public static final Texture IMG = new Texture(InvincibleOneMod.getResourcePath("relics/taskGoal.png"));
    public static final Texture OUTLINE_IMG = new Texture(InvincibleOneMod.getResourcePath("relics/taskGoal_outline.png"));

    public TaskGoal() {
        super(ID, IMG, OUTLINE_IMG, RelicTier.STARTER, LandingSound.FLAT);
    }

    public static int combatTurn = 0;
    // 上一把无敌者是否获胜
    private static boolean lastGameWin;
    // 上一把无敌者的认真度
    private static int lastGameA;
    private static boolean hasLastGame;
    public static void getLastGameHistory() {
        FileHandle[] runFolders = Gdx.files.local("runs" + File.separator).list();
        FileHandle runFile = Arrays.stream(runFolders)
                .filter(f ->
                        CardCrawlGame.saveSlot != 0 ? f.name().contains(CardCrawlGame.saveSlot + "_") :
                                !f.name().contains("0_") && !f.name().contains("1_") && !f.name().contains("2_"))
                .filter(FileHandle::isDirectory)
                .filter(f -> {
                    String characterName = CardCrawlGame.saveSlot != 0 ? f.name().substring(2) : f.name();
                    return CardCrawlGame.characterManager.getAllCharacters().stream().anyMatch(c -> c.chosenClass.name().equalsIgnoreCase(characterName));
                })
                .flatMap(f -> Arrays.stream(f.list()))
                .filter(f -> !f.isDirectory())
                .filter(f -> f.nameWithoutExtension().matches("\\d+"))
                .max(Comparator.comparing(f -> Integer.parseInt(f.nameWithoutExtension())))
                .orElse(null);

        if (runFile == null) {
            hasLastGame = false;
            return;
        }

        GsonBuilder builder = new GsonBuilder();
        if (CardModifierPatches.modifierAdapter == null) {
            CardModifierPatches.initializeAdapterFactory();
        }
        builder.registerTypeAdapterFactory(CardModifierPatches.modifierAdapter);
        Gson gson = builder.create();

        String runJson = runFile.readString();
        JsonObject rawData = gson.fromJson(runJson, JsonObject.class);
        RunData data = gson.fromJson(rawData, RunData.class);
        lastGameWin = data.victory;
        lastGameA = -data.ascension_level;
        hasLastGame = true;
    }

    @Override
    public String getUpdatedDescription() {
        return DESCRIPTIONS[0];
    }

    @Override
    public void onEnterRoom(AbstractRoom room) {
        if (AbstractDungeon.player instanceof InvincibleOne) {
            if (room instanceof TreasureRoomBoss && AbstractDungeon.id.equals(TheCity.ID)) {
                AbstractDungeon.player.masterDeck.group.forEach(card -> {
                    if (card.rarity == AbstractCard.CardRarity.BASIC && card.canUpgrade()) {
                        card.upgrade();
                        float x = MathUtils.random(0.1F, 0.9F) * (float) Settings.WIDTH;
                        float y = MathUtils.random(0.2F, 0.8F) * (float)Settings.HEIGHT;
                        AbstractDungeon.effectList.add(new ShowCardBrieflyEffect(card.makeStatEquivalentCopy(), x, y));
                        AbstractDungeon.topLevelEffects.add(new UpgradeShineEffect(x, y));
                    }
                });
                if (InvincibleOneMod.seriousLevel[CardCrawlGame.saveSlot] >= 2) {
                    AbstractDungeon.player.energy.energyMaster++;
                }
            }
            if (room instanceof TreasureRoomBoss && AbstractDungeon.id.equals(Exordium.ID)
                    && InvincibleOneMod.seriousLevel[CardCrawlGame.saveSlot] >= 1) {
                List<AbstractCard> cards = Skin.skinNow().getStartingCards();
                float cardWidth = Settings.WIDTH * 0.2F;
                for (AbstractCard card : cards) {
                    InvincibleOneMod.addToBot(new VFXAction(new ShowCardAndObtainEffect(card, cardWidth, Settings.HEIGHT / 2.0F, false), 0.3F));

                    cardWidth += Settings.WIDTH * 0.15F;
                }
                InvincibleOneMod.addToBot(new AbstractGameAction() {
                    @Override
                    public void update() {
                        isDone = AbstractDungeon.effectList.stream().noneMatch(effect -> effect instanceof ShowCardAndObtainEffect);
                        if (isDone) {
                            if (InvincibleOneMod.seriousLevel[CardCrawlGame.saveSlot] >= 8) {
                                List<AbstractCard> basicCardList = AbstractDungeon.player.masterDeck.group.stream().filter(c -> c.rarity == AbstractCard.CardRarity.BASIC).collect(Collectors.toList());
                                Collections.shuffle(basicCardList, new java.util.Random(AbstractDungeon.miscRng.randomLong()));
                                int amount = 0;
                                for (AbstractCard card : basicCardList) {
                                    if (amount < 3 && card.canUpgrade()) {
                                        card.upgrade();
                                        amount++;
                                    }
                                }
                            }
                        }
                    }
                });
            }
        }

        InvincibleOneMod.bingoPanel.getSlots().forEach(slot -> slot.getBingoItem().onEnterRoom(room));
        AbstractDungeon.player.masterDeck.group.forEach(c -> {
            if (c instanceof EnterRoomHook) {
                ((EnterRoomHook) c).onEnterRoom(room);
            }
        });

        Skin.skinNow().onEnterRoom(room);
    }

    public interface AlertLambda {
        void run();
    }
    public static class Alert implements Comparable<Alert> {
        private final int prior;
        public AlertLambda alertLambda;

        public Alert(int prior, AlertLambda alertLambda) {
            this.prior = prior;
            this.alertLambda = alertLambda;
        }

        @Override
        public int compareTo(Alert other) {
            return Integer.compare(other.prior, this.prior);
        }
    }
    public static PriorityQueue<Alert> alertQueue = new PriorityQueue<>();
    // 优先级越高越先执行。
    // 目前可在onBattleStart和onSpawnMonster中执行。
    // 理论上可能有alertQueue未清空而保留到之后的战斗中的情况。
    public static void AddToAlertQueue(int prior, AlertLambda alertLambda) {
        alertQueue.add(new Alert(prior, alertLambda));
    }

    // 战斗开始时的垃圾话
    public void atBattleStartTrashTalk() {
        Skin.trashTalked = false;
        List<String> collection = null;
        if (AbstractDungeon.floorNum == 1) {
            if (hasLastGame) {
                if (lastGameWin) {
                    if (lastGameA == 10) {
                        collection = Arrays.asList(Skin.TrashTalkGameStart1);
                    } else if (InvincibleOneMod.seriousLevel[CardCrawlGame.saveSlot] != 10) {
                        collection = Arrays.asList(Skin.TrashTalkGameStart4);
                    } else {
                        collection = Arrays.asList(Skin.TrashTalkGameStart5);
                    }
                } else {
                    collection = Arrays.asList(Skin.TrashTalkGameStart2);
                }
            } else {
                collection = Arrays.asList(Skin.TrashTalkGameStart3);
            }
        } else if (AbstractDungeon.floorNum == 18) {
            if (AbstractDungeon.player.currentHealth == AbstractDungeon.player.maxHealth) {
                collection = Arrays.asList(Skin.TrashTalkAct2_1);
            } else {
                collection = Arrays.asList(Skin.TrashTalkAct2_2);
            }
        } else if (AbstractDungeon.floorNum == 35) {
            collection = Arrays.asList(Skin.TrashTalkAct3);
        } else if (AbstractDungeon.getMonsters().monsters.stream().anyMatch(
                m -> m.id.equals(CorruptHeart.ID))) {
            if (InvincibleOneMod.provoked[CardCrawlGame.saveSlot]) {
                collection = Arrays.asList(Skin.TrashTalkFightTheHeartProvoked);
            } else {
                collection = Arrays.asList(Skin.TrashTalkFightTheHeart);
            }
        }

        if (collection != null) {
            Collections.shuffle(collection, new Random(AbstractDungeon.shuffleRng.randomLong()));
            Skin.TrashTalk(collection.get(0));
        }
    }

    @Override
    public void atBattleStart() {
        combatTurn = 0;

        atBattleStartTrashTalk();

        InvincibleOneMod.bingoPanel.getSlots().forEach(slot -> slot.getBingoItem().atBattleStart());
        if (InvincibleOneMod.seriousLevel[CardCrawlGame.saveSlot] >= 7) {
            AbstractPlayer p = AbstractDungeon.player;
            addToBot(new ApplyPowerAction(p, p, new StrengthPower(p, 2)));
            addToBot(new ApplyPowerAction(p, p, new MetallicizePower(p, 5)));
        }

        while (!alertQueue.isEmpty()) {
            Alert a = alertQueue.poll();
            a.alertLambda.run();
        }
    }

    @Override
    public void atTurnStart() {
        combatTurn++;

        InvincibleOneMod.bingoPanel.getSlots().forEach(slot -> slot.getBingoItem().atTurnStart());

        Skin.skinNow().atTurnStart();
    }

    @Override
    public int onAttackedToChangeDamage(DamageInfo dmg, int amount) {
        return Skin.skinNow().onAttackedToChangeDamage(dmg, amount);
    }

    @Override
    public void onPlayerEndTurn() {
        InvincibleOneMod.bingoPanel.getSlots().forEach(slot -> slot.getBingoItem().onPlayerEndTurn());
    }

    public boolean heartDead = false;
    @Override
    public void onVictory() {
        if (AbstractDungeon.getMonsters().monsters.stream().anyMatch(
                m -> m.id.equals(CorruptHeart.ID))) {
            heartDead = true;
            CardCrawlGame.stopClock = true;
            (AbstractDungeon.getCurrRoom()).rewards.clear();
            (AbstractDungeon.getCurrRoom()).rewardAllowed = false;
            AbstractDungeon.scene.fadeInAmbiance();
            CardCrawlGame.music.silenceTempBgmInstantly();
            CardCrawlGame.music.silenceBGMInstantly();
            return;
        }

        InvincibleOneMod.bingoPanel.getSlots().forEach(slot -> slot.getBingoItem().onVictory());
        Skin.skinNow().onVictory();
    }

//    @SpirePatch(clz = ProceedButton.class, method = "goToVictoryRoomOrTheDoor")
//    public static class HeartDeadPatch2 {
//        @SpireInsertPatch(rloc = 259 - 252, localvars = {"node"})
//        public static void PostFix(ProceedButton _instance, MapRoomNode node) {
//            node.room = new VictoryRoom(VictoryRoom.EventType.NONE);
//        }
//    }

    @SpirePatch(clz = ProceedButton.class, method = "update")
    public static class HeartDeadPatch {
        @SpireInsertPatch(rloc = 24)
        public static SpireReturn<Void> Insert(ProceedButton _instance) {
            if (AbstractDungeon.player instanceof InvincibleOne) {
                TaskGoal tg = (TaskGoal) AbstractDungeon.player.getRelic(ID);
                if (tg.heartDead) {
                    CardCrawlGame.music.fadeOutBGM();
                    MapRoomNode node = new MapRoomNode(3, 4);
                    node.room = (AbstractRoom)new TrueVictoryRoom();
                    AbstractDungeon.nextRoom = node;
                    AbstractDungeon.closeCurrentScreen();
                    AbstractDungeon.nextRoomTransitionStart();
//                    ReflectionHacks.RMethod rm = ReflectionHacks.privateMethod(ProceedButton.class, "goToVictoryRoomOrTheDoor");
//                    rm.invoke(_instance);
                    return SpireReturn.Return(null);
                }
            }
            return SpireReturn.Continue();
        }
    }

    @Override
    public int onAttacked(DamageInfo info, int damageAmount) {
        if (info.type == DamageInfo.DamageType.NORMAL) {
            if (damageAmount == 0 && (float)AbstractDungeon.player.currentHealth >= (float)AbstractDungeon.player.maxHealth * 0.9F) {
                if (InvincibleOneMod.provoked[CardCrawlGame.saveSlot]) {
                    List<String> collection = Arrays.asList(Skin.TrashTalkDamage5Provoked);
                    Collections.shuffle(collection, new Random(AbstractDungeon.shuffleRng.randomLong()));
                    Skin.TrashTalk(collection.get(0));
                } else {
                    List<String> collection = Arrays.asList(Skin.TrashTalkDamage5);
                    Collections.shuffle(collection, new Random(AbstractDungeon.shuffleRng.randomLong()));
                    Skin.TrashTalk(collection.get(0));
                }
            } else if (damageAmount >= 15) {
                List<String> collection = Arrays.asList(Skin.TrashTalkDamage15);
                Collections.shuffle(collection, new Random(AbstractDungeon.shuffleRng.randomLong()));
                String tt = collection.get(0);
                if (!tt.isEmpty()) {
                    Skin.TrashTalk(tt, true);
                }
            }
        }

        int tmp = damageAmount;
        for (BingoSlot slot : InvincibleOneMod.bingoPanel.getSlots()) {
            damageAmount = slot.getBingoItem().onAttacked(info, damageAmount);
        }
        return tmp;
    }

    @Override
    public void onUseCard(AbstractCard card, UseCardAction action) {
        InvincibleOneMod.bingoPanel.getSlots().forEach(slot -> slot.getBingoItem().onUseCard(card, action));
    }

    @Override
    public void onRest() {
        InvincibleOneMod.bingoPanel.getSlots().forEach(slot -> slot.getBingoItem().onRest());
        AbstractDungeon.player.masterDeck.group.forEach(c -> {
            if (c instanceof OnRestHook) {
                ((OnRestHook) c).onRest();
            }
        });
    }

    @Override
    public void onSpendGold() {
        InvincibleOneMod.bingoPanel.getSlots().forEach(slot -> slot.getBingoItem().onSpendGold());
    }

    @Override
    public void renderCounter(SpriteBatch sb, boolean inTopPanel) {
    }

    @Override
    public void onSpawnMonster(AbstractMonster monster) {
        for (SpawnMonsterHook spawnMonsterHook : AbstractDungeon.player.masterDeck.group.stream()
                .filter(c -> c instanceof SpawnMonsterHook)
                .map(c -> (SpawnMonsterHook) c)
                .collect(Collectors.toList())) {
            spawnMonsterHook.onSpawnMonster(monster);
        }
        while (!alertQueue.isEmpty()) {
            Alert a = alertQueue.poll();
            a.alertLambda.run();
        }
    }

    @Override
    public void onMonsterDeath(AbstractMonster m) {
        for (MonsterDeathHook monsterDeathHook : AbstractDungeon.player.masterDeck.group.stream()
                .filter(c -> c instanceof MonsterDeathHook)
                .map(c -> (MonsterDeathHook) c)
                .collect(Collectors.toList())) {
            monsterDeathHook.onMonsterDeath(m);
        }
    }

    @Override
    public void onRightClick() {
        InvincibleOneMod.bingoPanel.hide = !InvincibleOneMod.bingoPanel.hide;
        InvincibleOneMod.bingoRewardList.hide = InvincibleOneMod.bingoPanel.hide;
        if (InvincibleOneMod.bingoRewardList.getRewards().isEmpty()) {
            stopPulse();
        }
    }

    // 不会失去这个遗物
    @Override
    public void onUnequip() {
        new TaskGoal().instantObtain();
    }

    @Override
    public void onEquip() {
        beginLongPulse();
    }
}
